﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Data;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Stores;
using Nop.Services.Common;

namespace Nop.Services.Stores
{
    /// <summary>
    /// Store service
    /// </summary>
    public partial class StoreService : IStoreService
    {
        #region Constants
        private const string STORES_ALL_KEY = "Nop.store.all-{0}";
        private const string STORES_BY_ID_KEY = "Nop.store.id-{0}";
        private const string STORES_PATTERN_KEY = "Nop.store.";
        private const string STORES_FEATURED_KEY = "Nop.store.featured-{0}-{1}-{2}";
        private const string STOREBRANCH_BY_ID_KEY = "Nop.storebranch.id-{0}-{1}";
        #endregion

        #region Fields

        private readonly IRepository<Store> _storeRepository;
        private readonly IRepository<Branch> _branchRepository;
        private readonly IRepository<District> _districtRepository;
        private readonly IRepository<Region> _regionRepository;
        private readonly IRepository<Product> _productRepository;
        private readonly ICacheManager _cacheManager;
        private readonly IRepository<ShoppingCartItem> _shoppingCartItemsRepository;
        private readonly IGenericAttributeService _genericAttributeService;

        #endregion

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="cacheManager">Cache manager</param>
        /// <param name="storeRepository">Category repository</param>
        /// <param name="productStoreRepository">ProductCategory repository</param>
        /// <param name="productRepository">Product repository</param>
        public StoreService(ICacheManager cacheManager,
            IRepository<Store> storeRepository,
            IRepository<Branch> branchRepository,
            IRepository<Product> productRepository,
            IRepository<Region> regionRepository,
            IRepository<District> districtRepository,
            IRepository<ShoppingCartItem> ShoppingCartItemRepository,
            IGenericAttributeService genericAttributeService
                 )
        {
            this._cacheManager = cacheManager;
            this._storeRepository = storeRepository;
            this._branchRepository = branchRepository;
            this._productRepository = productRepository;
            this._regionRepository = regionRepository;
            this._shoppingCartItemsRepository = ShoppingCartItemRepository;
            this._genericAttributeService = genericAttributeService;
            this._districtRepository = districtRepository;
        }
        #endregion

        #region Methods

        /// <summary>
        /// Get shopping items per store
        /// </summary>
        /// <param name="shoppingCartItems">All shopping cart items</param>
        /// <returns>Orders per stores</returns>
        public virtual StoreOrder GetStoreShoppingCartItems(IList<ShoppingCartItem> shoppingCartItems)
        {
            var storeOrder = new StoreOrder();

            storeOrder.OrderGroupId = Guid.NewGuid();
            storeOrder.ShoppingCarts = shoppingCartItems
                .GroupBy(x => x.ProductVariant.Product.StoreId)
                .ToDictionary(g => GetStoreById(g.Key), g => g.ToList());

            storeOrder.StorePayments = new Hashtable(storeOrder.ShoppingCarts.Count);

            return storeOrder;
        }

        /// <summary>
        /// Deletes a store
        /// </summary>
        /// <param name="store">Store</param>
        public virtual void DeleteStore(Store store)
        {
            if (store == null)
                throw new ArgumentNullException("store");

            store.Deleted = true;
            UpdateStore(store);
        }

        /// <summary>
        /// Gets all stores
        /// </summary>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <param name="storeId">Store Id</param>
        /// <param name="adminCustomerId">Customer Id</param>
        /// <returns>Store collection</returns>
        public virtual IList<Store> GetAllStores(bool showHidden = false, bool showReserved = false, int adminCustomerId = 0)
        {
            string key = string.Format(STORES_ALL_KEY, showHidden);
            return _cacheManager.Get(key, () =>
            {
                var query = from m in _storeRepository.Table
                            orderby m.DisplayOrder
                            where (showHidden || m.Published) && !m.Deleted && (showReserved || m.Id != Store.AllStoreReservedId)
                                && (adminCustomerId == 0 || m.AdminCustomerId == adminCustomerId)
                            select m;
                var stores = query.ToList();
                return stores;
            });
        }

        /// <summary>
        /// Gets all stores
        /// </summary>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <param name="showReserved">Show system reserved stores</param>
        /// <param name="adminCustomerId">Store admin cusomter id</param>
        /// <returns>Stores</returns>
        public virtual IPagedList<Store> GetAllStores(int pageIndex, int pageSize, bool showHidden = false, bool showReserved = false, int adminCustomerId = 0)
        {
            var query = from m in _storeRepository.Table
                        orderby m.DisplayOrder
                        where (showHidden || m.Published) && !m.Deleted && (showReserved || m.Id != Store.AllStoreReservedId)
                            && (adminCustomerId == 0 || m.AdminCustomerId == adminCustomerId)
                        select m;

            return new PagedList<Store>(query, pageIndex, pageSize);
        }

        /// <summary>
        /// Gets featured stores
        /// </summary>
        /// <param name="categoryId">A value indicating category Id. Default = 0 for all categories</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <param name="showReserved">Show system reserved stores</param>
        /// <param name="recordsToReturn">Records to return</param>
        /// <returns>Store collection</returns>
        public virtual IList<Store> GetFeaturedStores(int categoryId, bool showHidden, bool showReserved, int recordsToReturn = 0)
        {
            string key = string.Format(STORES_FEATURED_KEY, showHidden, showReserved, categoryId);
            return _cacheManager.Get(key, () =>
            {
                IQueryable<Store> query;
                if (categoryId > 0)
                {
                    query = (from m in _storeRepository.Table
                             join p in _productRepository.Table on m.Id equals p.StoreId
                             orderby m.DisplayOrder
                             where m.Featured &&
                             (showHidden || m.Published) && !m.Deleted &&
                             (showReserved || m.Id != Store.AllStoreReservedId) &&
                             ((categoryId == 0) || (p.ProductCategories.Any(pc => pc.CategoryId == categoryId)))
                             select m).Distinct();
                }
                else
                {
                    query = from m in _storeRepository.Table
                            orderby m.DisplayOrder
                            where m.Featured &&
                            (showHidden || m.Published) && !m.Deleted &&
                            (showReserved || m.Id != Store.AllStoreReservedId)
                            select m;
                }

                if (recordsToReturn != 0 && recordsToReturn != int.MaxValue)
                    query = query.Take(recordsToReturn);

                var stores = query.ToList();
                return stores;
            });
        }
        /// <summary>
        /// Gets a store
        /// </summary>
        /// <param name="storeId">Store identifier</param>
        /// <returns>Store</returns>
        public virtual Store GetStoreById(int storeId)
        {
            if (storeId == 0)
                return null;

            string key = string.Format(STORES_BY_ID_KEY, storeId);
            return _cacheManager.Get(key, () =>
            {
                var store = _storeRepository.GetById(storeId);
                return store;
            });
        }

        /// <summary>
        /// Inserts a store
        /// </summary>
        /// <param name="store">Store</param>
        public virtual void InsertStore(Store store)
        {
            if (store == null)
                throw new ArgumentNullException("store");

            _storeRepository.Insert(store);

            //cache
            _cacheManager.RemoveByPattern(STORES_PATTERN_KEY);
        }

        /// <summary>
        /// Updates the store
        /// </summary>
        /// <param name="store">Store</param>
        public virtual void UpdateStore(Store store)
        {
            if (store == null)
                throw new ArgumentNullException("store");

            _storeRepository.Update(store);

            //cache
            _cacheManager.RemoveByPattern(STORES_PATTERN_KEY);
        }

        /// <summary>
        /// Update store review totals
        /// </summary>
        /// <param name="product">Store ID</param>
        public virtual void UpdateProductReviewTotals(int storeId)
        {
            // TODO: Check performance here
            var store = storeId == Store.AllStoreReservedId ? null : _storeRepository.GetById(storeId);
            if (null == store)
                throw new ArgumentException("storeId");

            int approvedRatingSum = 0;
            int notApprovedRatingSum = 0;
            int approvedTotalReviews = 0;
            int notApprovedTotalReviews = 0;

            var products = from m in _productRepository.Table
                           where m.StoreId == storeId
                           select m;

            foreach (var product in products)
            {
                // Suppose the product review totals are up to date

                //var reviews = product.ProductReviews;
                //foreach (var pr in reviews)
                //{
                //    if (pr.IsApproved)
                //    {
                //        approvedRatingSum += pr.Rating;
                //        approvedTotalReviews++;
                //    }
                //    else
                //    {
                //        notApprovedRatingSum += pr.Rating;
                //        notApprovedTotalReviews++;
                //    }
                //}


                approvedRatingSum += product.ApprovedRatingSum;
                approvedTotalReviews += product.ApprovedTotalReviews;
                notApprovedRatingSum += product.NotApprovedRatingSum;
                notApprovedTotalReviews += product.NotApprovedTotalReviews;
            }

            store.ApprovedRatingSum = approvedRatingSum;
            store.NotApprovedRatingSum = notApprovedRatingSum;
            store.ApprovedTotalReviews = approvedTotalReviews;
            store.NotApprovedTotalReviews = notApprovedTotalReviews;
            UpdateStore(store);
        }

        /// <summary>
        /// Gets if store exists
        /// </summary>
        /// <param name="storeId">Store ID</param>
        /// <param name="includingDeleted">Including deleted store</param>
        /// <returns></returns>
        public bool Exists(int storeId, bool includingDeleted = false)
        {
            if (storeId == 0)
                return false;

            string key = string.Format(STORES_BY_ID_KEY, storeId);
            var s = _cacheManager.Get(key, () =>
            {
                var store = _storeRepository.GetById(storeId);
                return store;
            });
            return (s != null && (includingDeleted || !s.Deleted));
        }

        /// <summary>
        /// Gets a store branch
        /// </summary>
        /// <param name="storeId">Store identifier</param>
        /// <param name="branchId">Branch identifier, where 0 means the first branch in DB ordered by ID</param>
        /// <returns>Branch</returns>
        public virtual Branch GetStoreBranchById(int storeId, int branchId)
        {
            if (storeId == 0)
                return null;

            string key = string.Format(STOREBRANCH_BY_ID_KEY, storeId, branchId);
            return _cacheManager.Get(key, () =>
            {
                var store = _storeRepository.GetById(storeId);

                Branch branch;
                if (branchId == 0)
                {
                    branch = store.Branches.OrderBy(b => b.Id).FirstOrDefault();
                }
                else
                {
                    branch = store.Branches.Where(b => b.Id == branchId).SingleOrDefault();
                }

                return branch;
            });
        }

        public virtual Branch GetBranchById(int branchId)
        {
            if (branchId <= 0) return null;

            return _branchRepository.GetById(branchId);
        }

        /// <summary>
        /// update branch
        /// </summary>
        /// <param name="branch">branch</param>
        /// <returns></returns>
        public void UpdateBranch(Branch branch)
        {
            if (branch == null) return;

            _branchRepository.Update(branch);
        }

        public IList<Store> GetFavoriteStores(Customer customer)
        {
            var storeStr = customer.GetAttribute<string>(SystemCustomerAttributeNames.FavoriteStore);
            if (String.IsNullOrEmpty(storeStr))
            {
                return new List<Store>();
            }

            var storeIdStrs = storeStr.Split(',');
            var stores = new List<Store>();
            foreach (var storeIdStr in storeIdStrs)
            {
                int id;
                if (int.TryParse(storeIdStr.Trim(), out id))
                {
                    var store = GetStoreById(id);
                    if (store == null)
                    {
                        _genericAttributeService.SaveAttribute<string>(customer, SystemCustomerAttributeNames.FavoriteStore, null);
                        throw new NopException("The favorite store doesn't exist. CustomerId : {0}, StoreId : {1}", customer.Id, id);
                    }
                    stores.Add(store);
                }
                else
                {
                    _genericAttributeService.SaveAttribute<string>(customer, SystemCustomerAttributeNames.FavoriteStore, null);
                    throw new NopException("The favorite store cannot be parsed. CustomerId : {0}, Data : {1}", customer.Id, storeStr);
                }
            }
            return stores;
        }

        public void AddFavoriteStore(int storeId, Customer customer)
        {
            var storeStr = customer.GetAttribute<string>(SystemCustomerAttributeNames.FavoriteStore);
            if (String.IsNullOrEmpty(storeStr))
            {
                _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FavoriteStore, storeId.ToString());
            }
            else
            {
                var storeIdStrs = storeStr.Split(',');
                var stores = new List<Store>();
                bool repeated = false;
                foreach (var storeIdStr in storeIdStrs)
                {
                    int id;
                    if (int.TryParse(storeIdStr.Trim(), out id))
                    {
                        if (storeId == id)
                        {
                            repeated = true;
                        }
                    }
                }
                if (repeated == false)
                {
                    _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FavoriteStore, storeStr + "," + storeId.ToString());
                }
            }
        }

        public void RemoveFavoriteStore(int storeId, Customer customer)
        {
            var storeStr = customer.GetAttribute<string>(SystemCustomerAttributeNames.FavoriteStore);
            if (!String.IsNullOrEmpty(storeStr))
            {
                var sid = storeId.ToString();
                var storeIdStrs = storeStr.Split(',').ToList();
                if (storeIdStrs.Remove(sid))
                {
                    storeStr = String.Join(",", storeIdStrs);
                    _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FavoriteStore, storeStr);
                    return;
                }
            }

            // Something wrong
            throw new NopException("The favorite store doesn't exist. CustomerId : {0}, StoreId : {1}", customer.Id, storeId);
        }

        public IEnumerable<Branch> GetStoreBranchByRegion(int storeId, int regionId)
        {
            if (regionId > 0)
            {
                var branches = from b in _branchRepository.Table
                               join d in _districtRepository.Table on b.DistrictId equals d.Id
                               where d.RegionId == regionId && b.StoreId == storeId
                               select b;

                return branches;
            }
            return new List<Branch>();
        }

        #endregion
    }
}
